// $Id: conversions.cpp 56 2007-05-31 20:18:18Z DarkAn9el $
/***************************************************************************
 *   Copyright (C) 2007 by Andrea Blankenstijn, Ulrich Giraud              *
 *   <darkan9el@gmail.com>, <blade.124@laposte.net>                        *
 *                                                                         *
 *   Permission is hereby granted, free of charge, to any person obtaining *
 *   a copy of this software and associated documentation files (the       *
 *   "Software"), to deal in the Software without restriction, including   *
 *   without limitation the rights to use, copy, modify, merge, publish,   *
 *   distribute, sublicense, and/or sell copies of the Software, and to    *
 *   permit persons to whom the Software is furnished to do so, subject to *
 *   the following conditions:                                             *
 *                                                                         *
 *   The above copyright notice and this permission notice shall be        *
 *   included in all copies or substantial portions of the Software.       *
 *                                                                         *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       *
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    *
 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*
 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR     *
 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR *
 *   OTHER DEALINGS IN THE SOFTWARE.                                       *
 ***************************************************************************/

#include "conversions.h"

/**********************
 * Méthodes publiques *
 **********************/

/**
 * Convertie le nombre vers la base X afin de pouvoir l'afficher
 * comme resultat d'un calcul ( dans le cas obase != 10 )
 * \return Nombre sous forme string converti en base X
 * \param[in] nb le nombre à convertir
 * \param[in] to_base la base de sortie, de 2 a 36
 */
const string Conversions::DecToX ( double nb, unsigned short to_base ) {
  double right_value=0, left_value=0;
  char tmpstr[200]={0}, converted_nb[200] = {0};
  int i = 0, j = -1, digit, flag=1;
  unsigned int k=0;
  string converted_value;

  if(nb < 0) {
    flag = -1;
    nb *= -1;
  }

  left_value = floor(nb); //on tronque le chiffre
  right_value = nb - left_value; // et la notre chiffre tronqué extrait la partie decimale

  do {
    digit = (intmax_t)((intmax_t)(left_value)%to_base);
    if(digit < 10)
      tmpstr[i] = (int)('0') + digit;
    else
      tmpstr[i] = (int)('A') + digit - 10;
    left_value = left_value / (double)(to_base);
    i++;
  } while ((intmax_t)(left_value) != 0);

  if (flag == -1) tmpstr[i++] = '-'; // replace le signe '-' en debut de chaine

  for ( ; i>=0 ; i--,j++ )
    converted_nb[j] = tmpstr[i]; // on inverse le tableau a cause de la conversion
  if (right_value != 0) converted_nb[j] = '.'; // on a fini la partie entiere

  for(i=j++ ; j<=i+10 ; j++) { // on prends juste 9 chiffres apres la virgule.
    right_value = right_value * (double)(to_base);
    if (right_value < 10)
      converted_nb[j] = (char)('0' + (intmax_t)(right_value)); // on tronque la right_value puis on affecte ca comme un char
    else
      converted_nb[j] = (char)('A' + right_value - 10); // car A = 10...
    right_value -= floor(right_value);
  }

  for ( ; j > 0 ; j-- ) { //Pour etre sur de pas segfaulter
    if (converted_nb[j] != '0') break; // a la chasse aux 0 inutiles
    converted_nb[j]='\0';
  }

  converted_value = converted_nb[0];
  for ( k = 1 ; k < strlen(converted_nb) ; k++ ) {
    converted_value.push_back(converted_nb[k]);
  }
  return converted_value;
}

/**
 * Convertie le nombre vers la base 10 afin de pouvoir facilement
 * effectuer les calculs.
 * \return double contenant le nombre convertis en base 10
 * \param[in] c_nb[] le nombre à convertir sous forme de char
 * \param[in] from_base dans quel base est c_nb, support de 2 à 36
 */
double Conversions::XToDec(char *c_nb, unsigned short from_base)
{
  short exp;
  p_nb = c_nb;
  double dec_nb;

  base = from_base;

  Size();

  // Alloue dynamiquement la mémoire pour nb_table selon la taille de nombre c_nb reçu
  nb_table = (unsigned short**) malloc( sizeof(unsigned short*)<<1 );
  nb_table[0] = (unsigned short*) malloc( size[0]*sizeof(unsigned short) );
  nb_table[1] = (unsigned short*) malloc( size[1]*sizeof(unsigned short) );

  ParseChar();

  // transformation vers la base 10
  exp = size[0] - 1;
  for(unsigned short i = 0; i < size[0]; i++)
  {
    dec_nb +=  nb_table[0][i] * pow((float)base, (int)exp);
    exp--;
  }

  exp = -1;
  for(unsigned short i = 0; i < size[1]; i++)
  {
    dec_nb += nb_table[1][i] * pow((float)base, (int)exp);
    exp--;
  }

  // Négativer ou garder positif le nombre
  dec_nb *= flag;

  free(nb_table[1]); free(nb_table[0]); free(nb_table);

  return dec_nb;
}

/********************
 * Méthodes privées *
 ********************/

/**
 * Vérifie la validité d'un chiffre dans la base donnée
 *
 * \return code d'erreur.
 * \param[in] to_check le chiffre à vérifier, peut être un chiffre ou une lettre.
 */
inline unsigned short Conversions::CheckChar(char to_check)
{
  // vérification que to_check existe bien dans la base demandée
  if(base < 11)
  {
    if((unsigned short) to_check - 48 < base )
      return 0;
    else
      return 1;
  }
  else if(base > 10)
  {
    if((unsigned short) to_check < base + 55 && (unsigned short) to_check < base + 87)
      return 0;
    else
      return 1;
  }
  // Au moins un des deux tests précédants devrais retourner une valeur
  // Donc cette ligne est _totalement_ inutile!
  // A moins d'avoir une erreur alien
  return 2;
}

/**
 * Convertie le caractère donné en un nombre short de base 10.
 *
 * \return le nombre convertie ou en cas d'erreur -1.
 * \param[in] c_nb le chiffre à convertir.
 */
inline short Conversions::ConvertChar(char c_nb)
{
  if( (unsigned short) c_nb >= 'A' && (unsigned short) c_nb <= 'Z' )
    return (unsigned short) c_nb - 55;
  else if( (unsigned short) c_nb >= 'a' && (unsigned short) c_nb <= 'z' )
    return (unsigned short) c_nb - 87;

  else if( (unsigned short) c_nb > 47 && (unsigned short) c_nb < 58 )
    return (unsigned short) c_nb - 48;

  else
    return -1;
}

/**
 * Traite le tableau char nb pour le transformer en tableau de short en base 10.
 *
 * \return fonction error de main.cc.
 */
void Conversions::ParseChar()
{
  unsigned short position = 0, indice = 0; // int_nb: le nombre de chiffres entiers ; fl_nb: le nombre de chiffres flottants
  short err_no; // pour stocker les valeurs de retour et checker si erreur il y a
  flag = 1;

  if(p_nb[0] == '-')
  {
    // Le while qui suit paniquerait si il verrait un caractère '-'...
    flag = -1;
    position++;
  }

  // On se tape la partie entière du nombre
  while(p_nb[position] != '\0')
  {
    if(p_nb[position] == '.')
    {
      position++;

      while(p_nb[position] != '\0')
      {
        err_no = CheckChar(p_nb[position]);
        if(err_no != 0)
          error("CheckChar: Caractère étranger à la base utilisée trouvé.");

        err_no = ConvertChar(p_nb[position]);
        if(err_no == -1)
          error("ConvertChar: Erreur à la conversion char->short d'un caractère.");
        else
          nb_table[1][indice] = err_no;

        position++;
        indice++;
      }

      break;
    }

    err_no = CheckChar(p_nb[position]);
    if(err_no != 0)
      error("CheckChar: Caractère étranger à la base utilisée trouvé.");

    err_no = ConvertChar(p_nb[position]);
    if(err_no == -1)
      error("ConvertChar: Erreur à la conversion char->short d'un caractère.");
    else
      nb_table[0][position] = err_no;

    position++;
  }
}

/**
 * Compte le nombre de chiffres entiers et flottants.
 *
 * Compte le nombre de chiffres entiers et flottants de nb pour pouvoir
 * par la suite créer un tableau de taille dynamique correspondant à nb
 * pour sa conversion du type char à un tableau de short.
 */
void Conversions::Size()
{
  unsigned short i=1;
  size[0] = 0, size[1] = 0;

  while(p_nb[size[0]] != '\0')
  {
    if(p_nb[size[0]] == '.')
    {
      i += size[0];
      while(p_nb[i] != '\0')
      {
        size[1]++;
        i++;
      }
      break;
    }
    size[0]++;
  }
}
